class Danmaku {
    constructor (options) {
        this.options = options;
        this.container = this.options.container;
        this.danTunnel = {
            right: {},
            top: {},
            bottom: {}
        };
        this.danIndex = 0;
        this.dan = [];
        this.showing = true;
        this._opacity = this.options.opacity;
        this.events = this.options.events;

        this.load();
    }

    load () {
        let apiurl;
        if (this.options.api.maximum) {
            apiurl = `${this.options.api.address}?id=${this.options.api.id}&max=${this.options.api.maximum}`;
        }
        else {
            apiurl = `${this.options.api.address}?id=${this.options.api.id}`;
        }
        const endpoints = (this.options.api.addition || []).slice(0);
        endpoints.push(apiurl);
        this.events && this.events.trigger('danmaku_load_start', endpoints);

        this._readAllEndpoints(endpoints, (results) => {
            this.dan = [].concat.apply([], results).sort((a, b) => a.time - b.time);
            window.requestAnimationFrame(() => {
                this.frame();
            });

            this.options.callback();

            this.events && this.events.trigger('danmaku_load_end');
        });
    }

    reload (newAPI) {
        this.options.api = newAPI;
        this.dan = [];
        this.clear();
        this.load();
    }

    /**
    * Asynchronously read danmaku from all API endpoints
    */
    _readAllEndpoints (endpoints, callback) {
        const results = [];
        let readCount = 0;
        const cbk = (i) => (err, data) => {
            ++readCount;
            if (err) {
                if (err.response) {
                    this.options.error(err.response.msg);
                }
                else {
                    this.options.error('Request was unsuccessful: ' + err.status);
                }
                results[i] = [];
            }
            else {
                results[i] = data;
            }
            if (readCount === endpoints.length) {
                return callback(results);
            }
        };

        for (let i = 0; i < endpoints.length; ++i) {
            this.options.apiBackend.read(endpoints[i], cbk(i));
        }
    }

    send (dan, callback) {
        const danmakuData = {
            token: this.options.api.token,
            player: this.options.api.id,
            author: this.options.api.user,
            time: this.options.time(),
            text: dan.text,
            color: dan.color,
            type: dan.type
        };
        this.options.apiBackend.send(this.options.api.address, danmakuData, callback);

        this.dan.splice(this.danIndex, 0, danmakuData);
        this.danIndex++;
        const danmaku = {
            text: this.htmlEncode(danmakuData.text),
            color: danmakuData.color,
            type: danmakuData.type,
            border: `2px solid ${this.options.borderColor}`
        };
        this.draw(danmaku);

        this.events && this.events.trigger('danmaku_send', danmakuData);
    }

    frame () {
        if (this.dan.length && !this.paused && this.showing) {
            let item = this.dan[this.danIndex];
            const dan = [];
            while (item && this.options.time() > parseFloat(item.time)) {
                dan.push(item);
                item = this.dan[++this.danIndex];
            }
            this.draw(dan);
        }
        window.requestAnimationFrame(() => {
            this.frame();
        });
    }

    opacity (percentage) {
        if (percentage !== undefined) {
            const items = this.container.getElementsByClassName('dplayer-danmaku-item');
            for (let i = 0; i < items.length; i++) {
                items[i].style.opacity = percentage;
            }
            this._opacity = percentage;
            localStorage.setItem('danmaku-opacity', this._opacity);

            this.events && this.events.trigger('danmaku_opacity', this._opacity);
        }
        return this._opacity;
    }

    /**
     * Push a danmaku into DPlayer
     *
     * @param {Object Array} dan - {text, color, type}
     * text - danmaku content
     * color - danmaku color, default: `#fff`
     * type - danmaku type, `right` `top` `bottom`, default: `right`
     */
    draw (dan) {
        const itemHeight = this.options.height;
        const danWidth = this.container.offsetWidth;
        const danHeight = this.container.offsetHeight;
        const itemY = parseInt(danHeight / itemHeight);

        const danItemRight = (ele) => {
            const eleWidth = ele.offsetWidth || parseInt(ele.style.width);
            const eleRight = ele.getBoundingClientRect().right || this.container.getBoundingClientRect().right + eleWidth;
            return this.container.getBoundingClientRect().right - eleRight;
        };

        const danSpeed = (width) => (danWidth + width) / 5;

        const getTunnel = (ele, type, width) => {
            const tmp = danWidth / danSpeed(width);

            for (let i = 0; this.options.unlimited || i < itemY; i++) {
                const item = this.danTunnel[type][i + ''];
                if (item && item.length) {
                    if (type !== 'right') {
                        continue;
                    }
                    for (let j = 0; j < item.length; j++) {
                        const danRight = danItemRight(item[j]) - 10;
                        if (danRight <= danWidth - tmp * danSpeed(parseInt(item[j].style.width)) || danRight <= 0) {
                            break;
                        }
                        if (j === item.length - 1) {
                            this.danTunnel[type][i + ''].push(ele);
                            ele.addEventListener('animationend', () => {
                                this.danTunnel[type][i + ''].splice(0, 1);
                            });
                            return i % itemY;
                        }
                    }
                }
                else {
                    this.danTunnel[type][i + ''] = [ele];
                    ele.addEventListener('animationend', () => {
                        this.danTunnel[type][i + ''].splice(0, 1);
                    });
                    return i % itemY;
                }
            }
            return -1;
        };

        if (Object.prototype.toString.call(dan) !== '[object Array]') {
            dan = [dan];
        }

        const docFragment = document.createDocumentFragment();

        for (let i = 0; i < dan.length; i++) {
            if (!dan[i].type) {
                dan[i].type = 'right';
            }
            if (!dan[i].color) {
                dan[i].color = '#fff';
            }
            const item = document.createElement(`div`);
            item.classList.add(`dplayer-danmaku-item`);
            item.classList.add(`dplayer-danmaku-${dan[i].type}`);
            if (dan[i].border) {
                item.innerHTML = `<span style="border:${dan[i].border}">${dan[i].text}</span>`;
            }
            else {
                item.innerHTML = dan[i].text;
            }
            item.style.opacity = this._opacity;
            item.style.color = dan[i].color;
            item.addEventListener('animationend', () => {
                this.container.removeChild(item);
            });

            const itemWidth = this._measure(dan[i].text);
            let tunnel;

            // adjust
            switch (dan[i].type) {
            case 'right':
                tunnel = getTunnel(item, dan[i].type, itemWidth);
                if (tunnel >= 0) {
                    item.style.width = itemWidth + 1 + 'px';
                    item.style.top = itemHeight * tunnel + 'px';
                    item.style.transform = `translateX(-${danWidth}px)`;
                }
                break;
            case 'top':
                tunnel = getTunnel(item, dan[i].type);
                if (tunnel >= 0) {
                    item.style.top = itemHeight * tunnel + 'px';
                }
                break;
            case 'bottom':
                tunnel = getTunnel(item, dan[i].type);
                if (tunnel >= 0) {
                    item.style.bottom = itemHeight * tunnel + 'px';
                }
                break;
            default:
                console.error(`Can't handled danmaku type: ${dan[i].type}`);
            }

            if (tunnel >= 0) {
                // move
                item.classList.add(`dplayer-danmaku-move`);

                // insert
                docFragment.appendChild(item);
            }
        }

        this.container.appendChild(docFragment);

        return docFragment;
    }

    play () {
        this.paused = false;
    }

    pause () {
        this.paused = true;
    }

    _measure (text) {
        if (!this.context) {
            const measureStyle = getComputedStyle(this.container.getElementsByClassName('dplayer-danmaku-item')[0], null);
            this.context = document.createElement('canvas').getContext('2d');
            this.context.font = measureStyle.getPropertyValue('font');
        }
        return this.context.measureText(text).width;
    }

    seek () {
        for (let i = 0; i < this.dan.length; i++) {
            if (this.dan[i].time >= this.options.time()) {
                this.danIndex = i;
                break;
            }
            this.danIndex = this.dan.length;
        }
    }

    clear () {
        this.danTunnel = {
            right: {},
            top: {},
            bottom: {}
        };
        this.danIndex = 0;
        this.options.container.innerHTML = '';

        this.events && this.events.trigger('danmaku_clear');
    }

    htmlEncode (str) {
        return str.
            replace(/&/g, "&amp;").
            replace(/</g, "&lt;").
            replace(/>/g, "&gt;").
            replace(/"/g, "&quot;").
            replace(/'/g, "&#x27;").
            replace(/\//g, "&#x2f;");
    }

    resize () {
        const danWidth = this.container.offsetWidth;
        const items = this.container.getElementsByClassName('dplayer-danmaku-item');
        for (let i = 0; i < items.length; i++) {
            items[i].style.transform = `translateX(-${danWidth}px)`;
        }
    }

    hide () {
        this.showing = false;
        this.pause();
        this.clear();

        this.events && this.events.trigger('danmaku_hide');
    }

    show () {
        this.seek();
        this.showing = true;
        this.play();

        this.events && this.events.trigger('danmaku_show');
    }
}

module.exports = Danmaku;
